# Python imports
import doctest
import logging
import os
import traceback

# AppEngine imports
from google.appengine.api import users
from google.appengine.ext.webapp import template 

from google.appengine.ext import db
from google.appengine.ext.db import djangoforms

# Django imports 
#from django.conf import settings
#settings._target = None
#os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
#import django
from django import http
from django import shortcuts

# Local imports
from forms import *
import models


def respond(request, user, template, params=None):
  """Helper to render a response, passing standard stuff to the response.

  Args:
    request: The request object.
    user: The User object representing the current user; or None if nobody
      is logged in.
    template: The template name; '.html' is appended automatically.
    params: A dict giving the template parameters; modified in-place.

  Returns:
    Whatever render_to_response(template, params) returns.

  Raises:
    Whatever render_to_response(template, params) raises.
  """
  if params is None:
    params = {}
  if user:
    params['user'] = user
    params['sign_out'] = users.create_logout_url('/')
    params['is_admin'] = (users.is_current_user_admin())
  else:
    params['sign_in'] = users.create_login_url(request.path)
  if not template.endswith('.html'):
    template += '.html'
  return shortcuts.render_to_response(template, params)

def execute_test_cases(request, test_cases, g):
  li = []
  solved = True
  for e in test_cases:
    if not e.want:
      exec e.source in g
      continue
    call = e.source.strip()
    got = eval(e.source.strip(), g)
    expected = eval(e.want, g)
    
    if got == expected:
      status = 'pass'
    else:
      status = 'fail'
      solved = False
    li.append([call, expected, "%r" % got, status])

  return li, solved


def index(request):
  user = users.get_current_user()
  return respond(request, user, 'index')

def contribution(request):
  user = users.get_current_user()
  return respond(request, user, 'contribution')
  
def help(request):
  user = users.get_current_user()
  return respond(request, user, 'help')
  
def about(request):
  user = users.get_current_user()
  return respond(request, user, 'about')

def vision(request):
  user = users.get_current_user()
  return respond(request, user, 'vision')


def get_pu(problem):
  '''
  returns:
   - "USER_NOT_SIGNED_IN": user not signed in. returning this to prevent saving anonymous user's potentially crappy solution and display it to every anonymous visitor
   - "PU_NOT_FOUND": for signed in users when no solution has been attempted.
   - ProblemUser: for signed in users when a solution has been attempted
  
  '''
  user = users.get_current_user()
  if not user:
    return "USER_NOT_SIGNED_IN"
  pu_query = models.ProblemUser.all().filter('problem = ', problem).filter('user =', users.get_current_user())
  pu_results = pu_query.fetch(1)
  if pu_results: 
    return pu_results[0]
  return "PU_NOT_FOUND"
   
def is_solved(problem):
  pu = get_pu(problem)
  if pu == "PU_NOT_FOUND" or pu == "USER_NOT_SIGNED_IN": return False
  return pu.solved

def problems(request, category=None):
  user = users.get_current_user()
  
  if category is None:
    problems = db.GqlQuery('SELECT * FROM Problem ORDER BY created DESC')
  else:
    problems = db.GqlQuery('SELECT * FROM Problem WHERE categories = :1 ORDER BY created DESC', category)

  entries = []
  for problem in problems:
    e = dict(problem=problem,
             username=problem.author.nickname().partition('@')[0],
             solved=is_solved(problem))
    entries.append(e)
  problems = entries
  return respond(request, user, 'problems', {'problems': problems, 'category': category})

def code(request, problem_id):
  problem = models.Problem.get(db.Key.from_path(models.Problem.kind(), int(problem_id)))

  if problem is None:
    return http.HttpResponseNotFound('No such problem.')
  
  pu = get_pu(problem)
  if pu == "PU_NOT_FOUND":  # user is attempting problem for the first time
    pu = models.ProblemUser(problem=problem, user=users.get_current_user(), solution='', solved=False)
    pu.put()
    pu = None

  return respond(request, users.get_current_user(), 'code', {'problem': problem, 'pu': pu})

def run(request):
  user = users.get_current_user()
  problem_id = request.POST.get('problem_id')
  if problem_id:
    problem = models.Problem.get(db.Key.from_path(models.Problem.kind(), int(problem_id)))
    if problem is None:
      return http.HttpResponseNotFound('No such problem.')
  
  user_code = request.POST.get('user_code')

  if not user_code:
    return http.HttpResponse('bad request')
  
  pu = get_pu(problem)
  # update ProblemUser object for this user
  if (pu.__class__ == models.ProblemUser):
    pu.solution = user_code
    pu.put()

  user_code = user_code.replace('\r\n', '\n')
  user_code += '\n\n'

  errors = ''
  try:
    compiled = compile(user_code, 'submitted code', 'exec')
    g = {}
    exec compiled in g

    s = problem.tests.replace('\r\n', '\n')
    s += '\n\n'
    test_cases = doctest.DocTestParser().get_examples(s) 
    results, solved = execute_test_cases(request, test_cases, g)
    if solved:
      pu = get_pu(problem)
      if pu.__class__ == models.ProblemUser:
        pu.solved = True
        pu.put()
    else:
      pu = get_pu(problem)
      if pu.__class__ == models.ProblemUser:
        pu.solved = False
        pu.put()
  except:
    errors = traceback.format_exc()
    return respond(request, user, 'traceback', {'errors': errors})
  
  return respond(request, users.get_current_user(), 'run', {'results': results})
  
def new_edit(request, problem_id=None):
  user = users.get_current_user()
  if user is None:
    return http.HttpResponseForbidden('You must be an signed in to create/edit a problem.')

  if problem_id is None:
    creating_new = True
  else:
    creating_new = False
    
  problem = None
  if problem_id:
    problem = models.Problem.get(db.Key.from_path(models.Problem.kind(), int(problem_id)))
    if problem.author != user and not users.is_current_user_admin():
      return http.HttpResponseForbidden('You can only edit your own problems.')
    if problem is None:
      return http.HttpResponseNotFound('No such problem.')

  form = ProblemForm(data=request.POST or None, instance=problem)
  if not request.POST:
    return respond(request, user, 'new_edit_problem', {'form': form, 'problem': problem})

  errors = form.errors
  if not errors:
    try:
      problem = form.save(commit=False)
    except ValueError, err:
      errors['__all__'] = unicode(err)
  if errors:
    return respond(request, user, 'new_edit_problem', {'form': form, 'problem': problem})

  if creating_new:
    problem.author = user
  l = []
  for x in problem.categories:
    l.extend(x.split())
  problem.categories = l
  problem.put()

  return http.HttpResponseRedirect('/')

